/* * Copyright (c) 2008, 2009, 2010 Denis Tulskiy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package com.tulskiy.musique.gui.dialogs; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultCellEditor; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import com.tulskiy.musique.gui.components.GroupTable; import com.tulskiy.musique.gui.cpp.TrackInfoItemSelection; import com.tulskiy.musique.gui.model.FileInfoModel; import com.tulskiy.musique.gui.model.MultiTagFieldModel; import com.tulskiy.musique.gui.model.SingleTagFieldModel; import com.tulskiy.musique.gui.model.Tools; import com.tulskiy.musique.gui.model.TrackInfoItem; import com.tulskiy.musique.gui.playlist.PlaylistTable; import com.tulskiy.musique.playlist.Track; import com.tulskiy.musique.playlist.TrackData; import com.tulskiy.musique.system.TrackIO; import com.tulskiy.musique.util.Util; /** * Author: Denis Tulskiy * Date: Jul 15, 2010 */ public class TracksInfoDialog extends JDialog { private JButton cancel; private PlaylistTable parent; private int DEFAULT_COLUMN_WIDTH = 430; public TracksInfoDialog(final PlaylistTable parent, final List<Track> tracks) { this.parent = parent; setTitle("Properties"); setModal(false); final MultiTagFieldModel tagFieldsModel = new MultiTagFieldModel(tracks); final JComponent tagsTable = createTable(tagFieldsModel); final JComponent propsTable = createTable(new FileInfoModel(tracks)); JTabbedPane tp = new JTabbedPane(); tp.setFocusable(false); tp.addTab("Metadata", tagsTable); tp.addTab("Properties", propsTable); add(tp, BorderLayout.CENTER); Box b1 = new Box(BoxLayout.X_AXIS); b1.add(Box.createHorizontalStrut(10)); final JButton tools = new JButton("Tools"); b1.add(tools); tools.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final JPopupMenu menu = new JPopupMenu(); JMenuItem menuItemEdit = new JMenuItem("Auto track number"); menu.add(menuItemEdit).addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Tools.autoTrackNumber(tagFieldsModel); tagFieldsModel.sort(); tagsTable.revalidate(); tagsTable.repaint(); } }); menu.show(tools, 0, tools.getBounds().height); } }); b1.add(Box.createHorizontalGlue()); JButton write = new JButton("Write"); b1.add(write); write.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tagFieldsModel.approveModel(); writeTracks(tracks); } }); cancel = new JButton("Cancel"); cancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { tagFieldsModel.rejectModel(); setVisible(false); dispose(); parent.requestFocus(); } }); b1.add(Box.createHorizontalStrut(5)); b1.add(cancel); b1.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 10)); add(b1, BorderLayout.SOUTH); setSize(600, 380); setLocationRelativeTo(SwingUtilities.windowForComponent(parent)); } private void writeTracks(final List<Track> tracks) { ProgressDialog dialog = new ProgressDialog(this, "Writing tags"); dialog.show(new Task() { String status; boolean abort = false; public int processed; @Override public boolean isIndeterminate() { return false; } @Override public float getProgress() { return (float) processed / tracks.size(); } @Override public String getStatus() { return "Writing Tags to: " + status; } @Override public void abort() { abort = true; } @Override public void start() { HashMap<File, ArrayList<Track>> cues = new HashMap<File, ArrayList<Track>>(); for (Track track : tracks) { TrackData trackData = track.getTrackData(); if (!trackData.isFile()) { processed++; continue; } if (abort) break; if (trackData.isCue()) { File file; if (trackData.isCueEmbedded()) { file = trackData.getFile(); } else { file = new File(trackData.getCueLocation()); } if (!cues.containsKey(file)) { cues.put(file, new ArrayList<Track>()); } cues.get(file).add(track); continue; } status = trackData.getFile().getName(); TrackIO.write(track); processed++; } // now let's write cue files // not implemented for now // CUEWriter writer = new CUEWriter(); // for (File file : cues.keySet()) { // status = file.getName(); // writer.write(file, cues.get(file)); // } parent.getPlaylist().firePlaylistChanged(); setVisible(false); dispose(); parent.requestFocus(); } }); } private JComponent createTable(TableModel model) { final GroupTable table = new GroupTable() { public Component prepareRenderer(final TableCellRenderer renderer, final int row, final int column) { final Component prepareRenderer = super .prepareRenderer(renderer, row, column); final TableColumn tableColumn = getColumnModel().getColumn(column); tableColumn.setPreferredWidth(Math.max( prepareRenderer.getPreferredSize().width + 20, tableColumn.getPreferredWidth())); tableColumn.setPreferredWidth(Math.max( DEFAULT_COLUMN_WIDTH, tableColumn.getPreferredWidth())); return prepareRenderer; } }; table.setModel(model); table.setFont(table.getFont().deriveFont(11f)); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); table.getColumn("Key").setMaxWidth(120); table.setShowVerticalLines(true); table.setIntercellSpacing(new Dimension(1, 1)); table.setGridColor(Color.lightGray); table.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); final JTextField editor = new JTextField(); table.setDefaultEditor(Object.class, new DefaultCellEditor(editor) { @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { TableModel tableModel = table.getModel(); if (tableModel instanceof MultiTagFieldModel) { if (((MultiTagFieldModel) tableModel).getTrackInfoItems().get(row).isMultiple()) { value = ""; } } JTextField c = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column); c.setBorder(BorderFactory.createEmptyBorder()); c.setFont(table.getFont()); c.selectAll(); return c; } @Override public void cancelCellEditing() { super.cancelCellEditing(); } @Override protected void fireEditingStopped() { TableModel tableModel = table.getModel(); if (tableModel instanceof MultiTagFieldModel) { String value = (String) table.getCellEditor().getCellEditorValue(); if (Util.isEmpty(value) && ((MultiTagFieldModel) tableModel).getTrackInfoItems().get(table.getEditingRow()).isMultiple()) { super.fireEditingCanceled(); return; } } super.fireEditingStopped(); } }); table.addKeyboardAction(KeyStroke.getKeyStroke("ENTER"), "startEditing", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { table.editCellAt(table.getSelectedRow(), 1); editor.requestFocusInWindow(); } }); table.addKeyboardAction(KeyStroke.getKeyStroke("DELETE"), "clearCell", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { table.getModel().setValueAt("", table.getSelectedRow(), 1); table.repaint(); } }); table.addKeyboardAction(KeyStroke.getKeyStroke("ESCAPE"), "exitOrStop", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (table.isEditing()) { table.getCellEditor().cancelCellEditing(); } else { cancel.doClick(); } } }); editor.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (table.isEditing() && ( e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_UP)) { table.getCellEditor().cancelCellEditing(); } } }); buildActions(table); table.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { show(e); } @Override public void mousePressed(MouseEvent e) { show(e); } public void show(MouseEvent e) { if (e.isPopupTrigger()) { int index = table.rowAtPoint(e.getPoint()); if (index != -1) { if (!table.isRowSelected(index)) { table.setRowSelectionInterval(index, index); } JPopupMenu contextMenu = buildContextMenu(parent, table); contextMenu.show(e.getComponent(), e.getX(), e.getY()); } } } }); JScrollPane scrollPane = new JScrollPane(table); scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); return scrollPane; } private MultiTagFieldModel getTagFieldModel(final GroupTable properties) { return (MultiTagFieldModel) properties.getModel(); } private List<TrackInfoItem> getSelectedItems(final GroupTable properties) { final List<TrackInfoItem> trackInfoItemsSelected = new LinkedList<TrackInfoItem>(); if (properties.getSelectedRowCount() > 0) { for (int row : properties.getSelectedRows()) { trackInfoItemsSelected.add(getTagFieldModel(properties).getTrackInfoItems().get(row)); } } return trackInfoItemsSelected; } private void buildActions(final GroupTable table) { ActionMap aMap = table.getActionMap(); InputMap iMap = table.getInputMap(table.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); aMap.put("edit", new AbstractAction("Edit") { @Override public void actionPerformed(ActionEvent e) { List<TrackInfoItem> selectedItems = getSelectedItems(table); TrackInfoItem firstItem = selectedItems.get(0); if (!selectedItems.isEmpty()) { final SingleTagFieldModel tagFieldModel = firstItem.getTracks().size() == 1 ? new SingleTagFieldModel(firstItem, firstItem.getTracks().get(0)) : new SingleTagFieldModel(firstItem); TracksInfoEditFieldDialog dialog = new TracksInfoEditFieldDialog(table, tagFieldModel); dialog.setVisible(true); } } }); aMap.put("capitalize", new AbstractAction("Capitalize") { @Override public void actionPerformed(ActionEvent e) { List<TrackInfoItem> selectedItems = getSelectedItems(table); if (!selectedItems.isEmpty()) { Tools.capitalize(selectedItems); table.revalidate(); table.repaint(); } } }); aMap.put("cut", new AbstractAction("Cut fields") { @Override public void actionPerformed(ActionEvent e) { List<TrackInfoItem> selectedItems = getSelectedItems(table); if (!selectedItems.isEmpty()) { TrackInfoItemSelection selection = new TrackInfoItemSelection(selectedItems); getToolkit().getSystemClipboard().setContents(selection, selection); getTagFieldModel(table).removeTrackInfoItems(selectedItems); refreshTable(table); } } }); aMap.put("copy", new AbstractAction("Copy fields") { @Override public void actionPerformed(ActionEvent e) { List<TrackInfoItem> selectedItems = getSelectedItems(table); if (!selectedItems.isEmpty()) { TrackInfoItemSelection selection = new TrackInfoItemSelection(selectedItems); getToolkit().getSystemClipboard().setContents(selection, selection); } } }); aMap.put("paste", new AbstractAction("Paste fields") { @Override public void actionPerformed(ActionEvent e) { List<TrackInfoItem> items = null; try { items = (List<TrackInfoItem>) getToolkit().getSystemClipboard().getContents(null). getTransferData(TrackInfoItemSelection.objectFlavor); } catch (IOException ioe) { // ignore, treating as the clipboard is empty } catch (UnsupportedFlavorException ufe) { // ignore since we already checked at menu construction that flavor is supported } if (items != null) { MultiTagFieldModel model = getTagFieldModel(table); model.mergeTrackInfoItems(items); model.sort(); refreshTable(table); } } }); aMap.put("add", new AbstractAction("Add") { @Override public void actionPerformed(ActionEvent e) { TracksInfoAddFieldDialog dialog = new TracksInfoAddFieldDialog(table, getTagFieldModel(table)); dialog.setVisible(true); refreshTable(table); } }); aMap.put("remove", new AbstractAction("Remove") { @Override public void actionPerformed(ActionEvent e) { List<TrackInfoItem> selectedItems = getSelectedItems(table); if (!selectedItems.isEmpty()) { getTagFieldModel(table).removeTrackInfoItems(selectedItems); refreshTable(table); } } }); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK + ActionEvent.SHIFT_MASK), "cut"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK + ActionEvent.SHIFT_MASK), "copy"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK + ActionEvent.SHIFT_MASK), "paste"); iMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK), "add"); } private JPopupMenu buildContextMenu(final PlaylistTable playlist, final GroupTable properties) { boolean isAnyRowSelected = properties.getSelectedRowCount() > 0; Transferable clipboardContent = getToolkit().getSystemClipboard().getContents(null); ImageIcon emptyIcon = new ImageIcon(new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB)); final JPopupMenu menu = new JPopupMenu(); JMenuItem menuItemEdit = new JMenuItem("Edit"); menuItemEdit.setIcon(emptyIcon); menuItemEdit.setEnabled(isAnyRowSelected); menu.add(menuItemEdit).addActionListener(properties.getActionMap().get("edit")); JMenuItem menuItemCapitalize = new JMenuItem("Capitalize"); menuItemCapitalize.setIcon(emptyIcon); menuItemCapitalize.setEnabled(isAnyRowSelected); menu.add(menuItemCapitalize).addActionListener(properties.getActionMap().get("capitalize")); menu.addSeparator(); JMenuItem menuItemCut = new JMenuItem("Cut fields"); menuItemCut.setIcon(emptyIcon); menuItemCut.setEnabled(isAnyRowSelected); menuItemCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK + ActionEvent.SHIFT_MASK)); menu.add(menuItemCut).addActionListener(properties.getActionMap().get("cut")); JMenuItem menuItemCopy = new JMenuItem("Copy fields"); menuItemCopy.setIcon(emptyIcon); menuItemCopy.setEnabled(isAnyRowSelected); menuItemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK + ActionEvent.SHIFT_MASK)); menu.add(menuItemCopy).addActionListener(properties.getActionMap().get("copy")); JMenuItem menuItemPaste = new JMenuItem("Paste fields"); menuItemPaste.setIcon(emptyIcon); menuItemPaste.setEnabled(clipboardContent != null && clipboardContent.isDataFlavorSupported(TrackInfoItemSelection.objectFlavor)); menuItemPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK + ActionEvent.SHIFT_MASK)); menu.add(menuItemPaste).addActionListener(properties.getActionMap().get("paste")); menu.addSeparator(); JMenuItem menuItemAdd = new JMenuItem("Add"); menuItemAdd.setIcon(emptyIcon); menuItemAdd.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK)); menu.add(menuItemAdd).addActionListener(properties.getActionMap().get("add")); JMenuItem menuItemRemove = new JMenuItem("Remove"); menuItemRemove.setIcon(emptyIcon); menuItemRemove.setEnabled(isAnyRowSelected); menu.add(menuItemRemove).addActionListener(properties.getActionMap().get("remove")); return menu; } private void refreshTable(JTable table) { table.clearSelection(); table.revalidate(); table.repaint(); } }